package tictacstego;

/**
 * Project 4 -- TTTAgent
 * 
 * This class implements an Agent that plays
 * Tic-Tac-Toe to send and receive a secret message.
 * 
 * @date Put the date you completed the class here.
 * 
 */

import java.util.LinkedList;

import binary.Binary;
import interfaces.Agent;
import interfaces.GameBoard;

public class TTTAgent implements Agent {
    // THIS AGENT'S SECRET MESSAGE IN BINARY
    // declare an instance variable to store the bits of the secret message
    private String   mssg;
    // THE OPPONENT'S SECRET MESSAGE IN BINARY
    // declare an instance variable to store the bits received from the opponent
    private String   mssgRCV;
    // THE PREVIOUS BOARD
    // declare an instance variable to store the previous board
    private TTTBoard prevBoard;

    /**
     * Construct a new TTTAgent to send a secret message
     * 
     * @param asciiMssg
     *            - the ASCII-encoded secret message to send
     */
    public TTTAgent(String asciiMssg) {
        // convert asciiMssg to binary and store the result
        mssg = Binary.ascii2bin(asciiMssg);
        // set bits received from opponent to the empty string ""
        mssgRCV = "";
        // set the previous board to null
        prevBoard = null;
    }

    @Override
    public void newGame() {
        prevBoard = null;
        // set the previous board to null
    }

    @Override
    public boolean askNewGame() {
        // return true only if this agent has more bits to send
        if (mssg.length() > 0)
            return true;
        else
            return false; // replace with your own return statement
    }

    @Override
    public String getMove(GameBoard b) {
        // SELECT OUR NEXT MOVE USING OUR SECRET BITS
        // find the good moves on the board
        String[] myGoodMoves = findMoves((TTTBoard) b);
        // compute the capacity of the choice between moves
        int myCapacity = (int) (Math.log(myGoodMoves.length) / Math.log(2));
        // get the next bits of the secret message
        String myBitsToSend = getNextBits(myCapacity);
        // convert the secret bits to an integer n
        int myMoveIndex = Binary.bits2int(myBitsToSend);
        // select the move whose index is n
        String move = myGoodMoves[myMoveIndex];

        // EXTRACT BITS FROM OPPONENT'S LAST MOVE
        // get the last move from the board
        String wtfLastMove = b.getLastMove();
        // if previous board is null
        if (prevBoard == null)
            // construct a new TTTBoard and store it as the previous board
            prevBoard = new TTTBoard();
        // if the last move is not null
        if (wtfLastMove != null) {
            // find the good moves on the previous board
            String[] wtfPrevGoodMoves = findMoves(prevBoard);
            // compute the capacity of the choice between moves
            int wtfPrevCapacity = (int) (Math.log(wtfPrevGoodMoves.length) / Math.log(2));
            // find the move that the opponent chose
            int wtfPrevMoveIndex = -1;
            for (int i = 0; i < wtfPrevGoodMoves.length; ++i) {
                if (wtfLastMove.equals(wtfPrevGoodMoves[i]))
                    wtfPrevMoveIndex = i;
            }
            // convert that move into some bits of secret message
            String wtfBitsReceived = Binary.int2bits(wtfPrevMoveIndex,
                    wtfPrevCapacity);
            // add those bits to the message received from opponent
            mssgRCV = mssgRCV.concat(wtfBitsReceived);
            // update the previous board with the opponent's chosen move
            prevBoard.playMove(wtfLastMove, b.getCurrentPlayer() == 1 ? 2 : 1);
        }
        // update the previous board with our chosen move
        prevBoard.playMove(move, b.getCurrentPlayer());
        // return our chosen move
        return move; // replace with your own return statement
    }

    /**
     * Finds the good moves on the given board. Good moves are those with which
     * the player can win or block, if possible.
     * 
     * @param b
     *            - current state of the board
     * @return String[] - an array of good moves on the specified board
     */
    public String[] findMoves(TTTBoard b) {
        // get the array of available moves from the board
        // use a LinkedList to keep track of good moves
        LinkedList<String> goodMoves = new LinkedList<String>();

        // FIRST, TRY TO WIN
        // for each available move
        // construct a copy of the current board
        // play that move on the board
        // if the board is in a winning state
        // add the move to the list of good moves
        // if the list of good moves is not empty
        // return the list of good moves as an array
        for (String move : b.getAvailableMoves()) {
            TTTBoard testBoard = new TTTBoard(b);
            testBoard.playMove(move);
            if (testBoard.hasWin())
                goodMoves.add(move);
        }
        if (!goodMoves.isEmpty()) {
            String[] ret = new String[goodMoves.size()];
            goodMoves.toArray(ret);
            return ret;
        }

        // THEN, TRY TO BLOCK
        // for each available move
        // construct a copy of the current board
        // play the move on the board copy as the opponent
        // if the board is in a winning state
        // add the move to the list of good moves
        // if the list of good moves is not empty
        // return the list of good moves as an array
        for (String move : b.getAvailableMoves()) {
            TTTBoard testBoard = new TTTBoard(b);
            testBoard.playMove(move, testBoard.getCurrentPlayer() == 1 ? 2 : 1);
            if (testBoard.hasWin())
                goodMoves.add(move);
        }
        if (!goodMoves.isEmpty()) {
            String[] ret = new String[goodMoves.size()];
            goodMoves.toArray(ret);
            return ret;
        }

        // FINALLY, JUST PLAY ANYWHERE
        // return the array of available moves
        return b.getAvailableMoves(); // replace with your own return statement
    }

    /**
     * Extracts the next n bits of the secret message
     * 
     * @param n
     *            - number of bits to get from the secret message
     * @return String - the next n bits of the secret message
     */
    public String getNextBits(int n) {
        if (n == 0)
            return "";

        // if the message is not as long as the number of bits asked for
        // pad the RHS of the message with 0s
        if (mssg.length() < n) {
            for (int i = 0; i < n; ++i)
                mssg += '0';
        }

        // get the first n bits of the message
        String ret = mssg.substring(0, n);
        // update the message by removing the first n bits
        mssg = mssg.substring(n, mssg.length());
        // return the first n bits of the message
        return ret; // replace with your own return statement
    }

    /**
     * Returns the ASCII-encoded message that was received from the opponent
     * 
     * @return String - ASCII-encoded received message
     */
    public String getRCVdMssg() {
        // convert bits received from opponent to ASCII
        // trim trailing whitespace from the message
        // return the message
        String ret;
        ret = Binary.bin2ascii(mssgRCV);

        if (ret.length() == 0)
            return "";

        while (ret.charAt(ret.length() - 1) == 0) {
            ret = ret.substring(0, ret.length() - 1);
        }
        return ret; // replace with your own return statement
    }
}
